// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.ComponentModel;

using Tierless.Framework.Data;
using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.Entities
{
	/// <summary>
	/// Provides an abstract base class for the implementation of an entity column.
	/// </summary>
	public abstract class EntityColumn : IConvertible, INotifyPropertyChanged
	{
		/// <summary>
		/// Initializes a new instance of the EntityColumn class.
		/// </summary>
		/// <param name="entity">The entity to which the column belongs.</param>
		/// <param name="columnType">The type of the column.</param>
		/// <param name="columnIndex">The index of the column within the list of columns in the entity.</param>
		public EntityColumn(Entity entity, EntityTypeColumn columnType, int columnIndex)
		{
			Entity = entity;
			ColumnType = columnType;
			ColumnIndex = columnIndex;
		}

		/// <summary>
		/// Gets the entity to which the column belongs.
		/// </summary>
		public Entity Entity { get; private set; }

		/// <summary>
		/// Gets the type of the column.
		/// </summary>
		public EntityTypeColumn ColumnType { get; private set; }

		/// <summary>
		/// Gets the name of the column.
		/// </summary>
		public string Name
		{
			get { return ColumnType.Name; }
		}

		/// <summary>
		/// Gets the index of the column within the list of columns in the entity.
		/// </summary>
		public int ColumnIndex { get; private set; }

		/// <summary>
		/// Gets the value of the column as an object.
		/// </summary>
		public object Value
		{
			get { return Entity.InternalGetValue(ColumnIndex); }
			set { SetValue(value); }
		}

		/// <summary>
		/// Gets the original value of the column as an object.
		/// </summary>
		public object OriginalValue
		{
			get { return Entity.InternalGetOriginalValue(ColumnIndex); }
		}

		/// <summary>
		/// Gets whether or not the column has been modified.
		/// </summary>
		public bool IsModified
		{
			get { return Entity.IsModified && (!Equals(OriginalValue, Value)); }
		}

		/// <summary>
		/// Gets whether or not the column has a value.
		/// </summary>
		public bool IsNull
		{
			get { return Value == null; }
		}

		/// <summary>
		/// Returns the value of the column if there is one, otherwise the given default value.
		/// </summary>
		/// <param name="defaultValue">The value to be returned if the column is null.</param>
		/// <returns>The value of the column if it is not null, otherwise, the given default value.</returns>
		public object IfNull(object defaultValue)
		{
			return IsNull ? defaultValue : Value;
		}

		/// <summary>
		/// Returns the value of the column as a specific type, if the column has a value, otherwise, the given default value is returned.
		/// </summary>
		/// <typeparam name="T">The type of the value.</typeparam>
		/// <param name="defaultValue">The default value to be returned if the column is null.</param>
		/// <returns>The given default value if the column is null, otherwise, the value of the column cast as the specified type.</returns>
		public T IfNull<T>(T defaultValue)
		{
			return IsNull ? defaultValue : (T) Value;
		}

		protected virtual bool InternalIsEmpty()
		{
			return IsNull;
		}

		/// <summary>
		/// Gets whether or not the column is empty.
		/// </summary>
		public bool IsEmpty
		{
			get { return InternalIsEmpty(); }
		}

		/// <summary>
		/// Clears the value of the column.
		/// </summary>
		public void Clear()
		{
			Value = null;
		}

		protected void SetValue(object tempValue)
		{
			var isNull = IsNull;
			if (Entity.InternalSetValue(ColumnIndex, InternalSetValue(tempValue)))
			{
				OnPropertyChanged("Value");
				if (isNull != IsNull)
					OnPropertyChanged("IsNull");
			}
		}

		protected abstract object InternalSetValue(object tempValue);

		protected void ConvertValue(object tempValue)
		{
			var isNull = IsNull;
			if (Entity.InternalSetValue(ColumnIndex, InternalConvertValue(tempValue)))
			{
				OnPropertyChanged("Value");
				if (isNull != IsNull)
					OnPropertyChanged("IsNull");
			}
		}

		protected abstract object InternalConvertValue(object tempValue);

		protected abstract TypeCode InternalGetTypeCode();

		private Type _nativeType;

		/// <summary>
		/// Gets the native type of the column.
		/// </summary>
		public Type NativeType
		{
			get { return _nativeType ?? (_nativeType = Type.GetType(ColumnType.NativeTypeName, true)); }
		}

		private Type _bindingType;

		/// <summary>
		/// Gets the binding type of the column.
		/// </summary>
		public Type BindingType
		{
			get { return _bindingType ?? (_bindingType = InternalGetBindingType()); }
		}

		protected virtual Type InternalGetBindingType()
		{
			return NativeType;
		}

		private string _bindingPropertyName;

		/// <summary>
		/// Gets the binding property name of the column.
		/// </summary>
		public string BindingPropertyName
		{
			get
			{
				if (String.IsNullOrEmpty(_bindingPropertyName))
				{
					_bindingPropertyName = ColumnType.BindingPropertyName;
					if (String.IsNullOrEmpty(_bindingPropertyName))
						_bindingPropertyName = InternalGetBindingPropertyName();
				}

				return _bindingPropertyName;
			}
		}

		protected virtual string InternalGetBindingPropertyName()
		{
			return "Value";
		}

		private bool _isRequired;

		/// <summary>
		/// Gets or sets whether or not the column is required.
		/// </summary>
		public bool IsRequired
		{
			get { return ColumnType.DetermineIsRequired(Entity, _isRequired); }
			set
			{
				if (_isRequired != value)
				{
					_isRequired = value;
					OnPropertyChanged("IsRequired");
				}
			}
		}

		private bool _isReadOnly;

		/// <summary>
		/// Gets or sets whether or not the column is read only.
		/// </summary>
		public bool IsReadOnly
		{
			get { return ColumnType.DetermineIsReadOnly(Entity, _isReadOnly); }
			set
			{
				if (_isReadOnly != value)
				{
					_isReadOnly = value;
					OnPropertyChanged("IsReadOnly");
				}
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		protected virtual void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		/// <summary>
		/// Gets or sets the value of the column as a boolean.
		/// </summary>
		public virtual bool AsBoolean
		{
			get { return Convert.ToBoolean(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a byte.
		/// </summary>
		public virtual byte AsByte
		{
			get { return Convert.ToByte(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a character.
		/// </summary>
		public virtual char AsChar
		{
			get { return Convert.ToChar(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as an array of bytes.
		/// </summary>
		public virtual byte[] AsByteArray
		{
			get { return (byte[]) Value; }
			set { SetValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a date time.
		/// </summary>
		public virtual DateTime AsDateTime
		{
			get { return Convert.ToDateTime(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a decimal.
		/// </summary>
		public virtual decimal AsDecimal
		{
			get { return Convert.ToDecimal(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column for read contexts.
		/// </summary>
		public virtual string AsDisplayString
		{
			get { return Convert.ToString(Value); }
			set
			{
				if (String.IsNullOrEmpty(value))
					SetValue(null);
				else
					ConvertValue(value);
			}
		}

		/// <summary>
		/// Gets or sets the value of the column as a double.
		/// </summary>
		public virtual double AsDouble
		{
			get { return Convert.ToDouble(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a guid.
		/// </summary>
		public virtual Guid AsGuid
		{
			get { return IsNull ? Guid.Empty : (Guid) Value; }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a short.
		/// </summary>
		public virtual short AsShort
		{
			get { return Convert.ToInt16(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as an integer.
		/// </summary>
		public virtual int AsInteger
		{
			get { return Convert.ToInt32(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a long.
		/// </summary>
		public virtual long AsLong
		{
			get { return Convert.ToInt64(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a signed byte.
		/// </summary>
		public virtual sbyte AsSByte
		{
			get { return Convert.ToSByte(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a float.
		/// </summary>
		public virtual float AsSingle
		{
			get { return Convert.ToSingle(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as a string.
		/// </summary>
		public virtual string AsString
		{
			get { return Convert.ToString(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as an unsigned short.
		/// </summary>
		public virtual ushort AsUShort
		{
			get { return Convert.ToUInt16(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as an unsigned integer.
		/// </summary>
		public virtual uint AsUInteger
		{
			get { return Convert.ToUInt32(Value); }
			set { ConvertValue(value); }
		}

		/// <summary>
		/// Gets or sets the value of the column as an unsigned long.
		/// </summary>
		public virtual ulong AsULong
		{
			get { return Convert.ToUInt64(Value); }
			set { ConvertValue(value); }
		}

		#region IConvertible Members

		TypeCode IConvertible.GetTypeCode()
		{
			return InternalGetTypeCode();
		}

		bool IConvertible.ToBoolean(IFormatProvider provider)
		{
			return AsBoolean;
		}

		byte IConvertible.ToByte(IFormatProvider provider)
		{
			return AsByte;
		}

		char IConvertible.ToChar(IFormatProvider provider)
		{
			return AsChar;
		}

		DateTime IConvertible.ToDateTime(IFormatProvider provider)
		{
			return AsDateTime;
		}

		decimal IConvertible.ToDecimal(IFormatProvider provider)
		{
			return AsDecimal;
		}

		double IConvertible.ToDouble(IFormatProvider provider)
		{
			return AsDouble;
		}

		short IConvertible.ToInt16(IFormatProvider provider)
		{
			return AsShort;
		}

		int IConvertible.ToInt32(IFormatProvider provider)
		{
			return AsInteger;
		}

		long IConvertible.ToInt64(IFormatProvider provider)
		{
			return AsLong;
		}

		sbyte IConvertible.ToSByte(IFormatProvider provider)
		{
			return AsSByte;
		}

		float IConvertible.ToSingle(IFormatProvider provider)
		{
			return AsSingle;
		}

		string IConvertible.ToString(IFormatProvider provider)
		{
			return AsString;
		}

		object IConvertible.ToType(Type conversionType, IFormatProvider provider)
		{
			switch (conversionType.Name)
			{
				case "Boolean":
					return AsBoolean;
				case "Byte":
					return AsByte;
				case "Char":
					return AsChar;
				case "DateTime":
					return AsDateTime;
				case "Decimal":
					return AsDecimal;
				case "Double":
					return AsDouble;
				case "Int16":
					return AsShort;
				case "Int32":
					return AsInteger;
				case "Int64":
					return AsLong;
				case "SByte":
					return AsSByte;
				case "Single":
					return AsSingle;
				case "String":
					return AsString;
				case "UInt16":
					return AsUShort;
				case "UInt32":
					return AsUInteger;
				case "UInt64":
					return AsULong;
				default:
					throw new ArgumentException(String.Format("Cannot convert to type {0}.", conversionType.Name));
			}
		}

		ushort IConvertible.ToUInt16(IFormatProvider provider)
		{
			return AsUShort;
		}

		uint IConvertible.ToUInt32(IFormatProvider provider)
		{
			return AsUInteger;
		}

		ulong IConvertible.ToUInt64(IFormatProvider provider)
		{
			return AsULong;
		}

		#endregion

		public override string ToString()
		{
			return String.Format("{0} : {1} ({2})", Name, NativeType.FullName, IsNull ? "<no value>" : Value);
		}

		public override bool Equals(object obj)
		{
			var other = obj as EntityColumn;
			return Object.ReferenceEquals(other, null) ? base.Equals(obj) : Equals(Value, other.Value);
		}

		public static bool operator ==(EntityColumn left, EntityColumn right)
		{
			if (Object.ReferenceEquals(left, null)) throw new ArgumentNullException("ALeft");
			if (Object.ReferenceEquals(right, null)) throw new ArgumentNullException("ARight");
			
			return Equals(left.Value, right.Value);
		}

		public static bool operator !=(EntityColumn left, EntityColumn right)
		{
			if (Object.ReferenceEquals(left, null)) throw new ArgumentNullException("ALeft");
			if (Object.ReferenceEquals(right, null)) throw new ArgumentNullException("ARight");
			
			return !Equals(left.Value, right.Value);
		}

		public override int GetHashCode()
		{
			return Value == null ? 0 : Value.GetHashCode();
		}
	}
}